//////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) Tobias Zirr. All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "D3D11EffectsLite.h"
#include "Heap.h"
#include <cassert>

namespace D3DEffectsLite
{

Heap::Heap()
	: m_bytes(),
	m_byteCount(),
	m_top()
{
}

Heap::~Heap()
{
	GetGlobalAllocator()->Free(m_bytes);
}

namespace
{

inline size_t CheckedUIntSizeAlign(size_t size)
{
	if (size > size_t(-1) - HeapAlignment)
		throw OutOfBounds();

	return AlignInteger<size_t>(size, HeapAlignment);
}

} // namespace

void Heap::PreAllocate(size_t size)
{
	assert(m_bytes == nullptr);

	size_t alignedSize = CheckedUIntSizeAlign(size);

	if (m_byteCount > size_t(-1) - alignedSize)
		throw OutOfBounds();

	m_byteCount += alignedSize;
}

void Heap::Allocate()
{
	assert(m_bytes == nullptr);

	m_bytes = (char*) GetGlobalAllocator()->Allocate(m_byteCount + HeapAlignment);

	if (!m_bytes)
		throw OutOfMemory();
}

void* Heap::PostAllocate(size_t size)
{
	assert(m_bytes != nullptr);

	size_t alignedSize = CheckedUIntSizeAlign(size);

	if (alignedSize > m_byteCount || m_top > m_byteCount - alignedSize)
		throw OutOfBounds();

	void *memory = Align(m_bytes, HeapAlignment) + m_top;
	m_top += alignedSize;
	return memory;
}

void* Heap::Reserve(size_t size)
{
	return (!PreAllocation())
		? PostAllocate(size)
		: (PreAllocate(size), nullptr);
}

ChunkHeap::ChunkHeap(Allocator *allocator, size_t chunkSize)
		: allocator(allocator)
		, m_chunk(nullptr)
		, m_chunkOffset(nullptr)
		, m_chunkEnd(nullptr)
		, m_nextChunkSize(chunkSize)
{
}

ChunkHeap::~ChunkHeap()
{
	// Free as many chunks as possible
	for (char *chunk = m_chunk; chunk; )
	{
		chunk_header *freeChunkBase = to_chunk_header(chunk);
		chunk = freeChunkBase->prev_chunk;
		allocator->Free(freeChunkBase);
	}
}

void* ChunkHeap::Allocate(size_t size)
{
	size_t remaining_capacity = size_t(m_chunkEnd - m_chunkOffset);

	// Allocate new chunk, if old chunk too small
	if (size > remaining_capacity)
	{
		// Make sure new chunk is large enough for requested amount of memory + alignment + header
		size_t nextChunkSize = max(size + (HeapAlignment - 1), m_nextChunkSize);
		nextChunkSize += sizeof(chunk_header);

		char *nextChunkBase = (char*) allocator->Allocate(nextChunkSize);
		new((void*) nextChunkBase) chunk_header(m_chunk);
		m_chunk = nextChunkBase + sizeof(chunk_header);
		m_chunkEnd = nextChunkBase + nextChunkSize;

		m_chunkOffset = Align(m_chunk, HeapAlignment);
		remaining_capacity = size_t(m_chunkEnd - m_chunkOffset);
	}

	// Next aligned block of memory
	char *memory = m_chunkOffset;
	m_chunkOffset += min(AlignInteger<size_t>(size, HeapAlignment), remaining_capacity);
	return memory;
}

} // namespace
